package models;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import controllers.Events;
import controllers.Users;

import play.db.jpa.Model;
import play.modules.search.Field;
import play.modules.search.Indexed;

@Indexed
@Entity
@Table(name = "Events")
public class Event extends Model {

	@Field
	public String name;

	@Field
	public String description;

	public String location;

	public boolean status;

	public double maxEffortPoints;

	@Column(name = "start_date")
	public Calendar startDate;

	@Column(name = "end_date")
	public Calendar endDate;

	public Calendar duration;

	@Column(name = "attendee")
	public int numOfAttendee;

	@ManyToMany
	@JoinTable(name = "Events_UserStories", joinColumns = @JoinColumn(name = "event_id"), inverseJoinColumns = @JoinColumn(name = "story_id"), uniqueConstraints = { @UniqueConstraint(columnNames = {
			"event_id", "story_id" }) })
	public List<UserStory> userStories = new LinkedList<UserStory>();

	@OneToOne(mappedBy = "event", cascade = { CascadeType.REMOVE })
	public VirtualBoard board;

	@OneToMany(mappedBy = "event", cascade = { CascadeType.REMOVE })
	public List<Snapshot> Snapshots;

	@ManyToOne
	public Project project;

	@ManyToOne
	public EventType type;

	@ManyToOne
	public User user;

	@ManyToMany
	@JoinTable(name = "Events_Notes", joinColumns = @JoinColumn(name = "event_id"), inverseJoinColumns = @JoinColumn(name = "note_id"), uniqueConstraints = { @UniqueConstraint(columnNames = {
			"event_id", "note_id" }) })
	public List<Note> notes = new LinkedList<Note>();

	@ManyToMany
	@JoinTable(name = "Events_Tasks", joinColumns = @JoinColumn(name = "event_id"), inverseJoinColumns = @JoinColumn(name = "task_id"), uniqueConstraints = { @UniqueConstraint(columnNames = {
			"event_id", "task_id" }) })
	public List<Task> tasks = new LinkedList<Task>();

	@ManyToOne
	public Event sprint;

	@OneToMany(mappedBy = "sprint")
	public List<Event> events = new LinkedList<Event>();

	@OneToMany(mappedBy = "event", cascade = { CascadeType.REMOVE })
	public List<EventUser> eventAttendees = new LinkedList<EventUser>();

	/**
	 * sets the start date of an event
	 * 
	 * @param startDate
	 */
	public void setStartDate(Calendar startDate) {
		this.startDate = startDate;

		// Set the duration ' To be implemented'
	}

	/**
	 * constructs an empty event
	 */
	public Event() {
	}

	/**
	 * constructs an event with a description and type
	 * 
	 * @param description
	 * @param type
	 */
	public Event(String description, EventType type) {
		this.description = description;
		this.type = type;
	}

	/**
	 * constructs an event with name, description, location , start date and end
	 * date
	 * 
	 * @param name
	 * @param description
	 * @param location
	 * @param startDate
	 * @param endDate
	 */
	public Event(String name, String description, String location,
			Calendar startDate, Calendar endDate) {
		this.name = name;
		this.description = description;
		this.location = location;
		this.startDate = startDate;
		this.endDate = endDate;
	}

	/**
	 * constructs an event with a name, description and a list of tasks
	 * associatd to it
	 * 
	 * @param name
	 * @param description
	 * @param tasks
	 */
	public Event(String name, String description, List<Task> tasks) {
		this.name = name;
		this.description = description;
		this.tasks = tasks;
	}

	/**
	 * Returns the project of this event
	 * 
	 * @author Loai_Ghoraba
	 * @return the project of this event
	 */
	public Project getProject() {
		if (this.project == null)
			return sprint.project;
		return project;
	}

	public Event delete() {
		// Delete events
		for (Event event : events)
			event.delete();
		// Associate tasks to the default event
		EventType defaultType = EventType.find("byType", "Default").first();
		if (type != defaultType)
			for (Task task : tasks)
				if (task.events.size() == 1)// If it is associated to this event
					// only
					associateToDefault(task);
		return super.delete();
	}

	public void associateToDefault(Task t) {
		EventType et = EventType.find("byType", "Default").first();
		Project p = getProject();
		Event defaultEvent = Event.find(
				"select e from Event e where e.project=? and e.type=? ", p, et)
				.first();
		if (defaultEvent != null) {
			if (!defaultEvent.tasks.contains(t)) {
				defaultEvent.tasks.add(t);
				t.events.add(defaultEvent);
				defaultEvent.save();
				t.save();
			}
		}
	}

	/**
	 *Deassociate a task from an event with out adding it to the default event
	 * 
	 * @author Loai_Ghoraba
	 *@param event
	 *            the event
	 *@param task
	 *            the task
	 * 
	 */
	public boolean removeTask(Task task) {
		this.tasks.remove(task);
		task.events.remove(this);
		task.save();
		return true;
	}

	/**
	 * This method returns a list of all Tasks in an event with a certain type
	 * 
	 * @param eventId
	 * @param taskType
	 * 
	 * @return List<Task>
	 */
	public List<Task> getEventTasksOfType(String taskType) {
		List<Task> tasksList = this.getAssociatedwithEvent("T");

		if (tasksList == null)
			return new LinkedList<Task>();

		int i = 0;
		while (i < tasksList.size()) {
			if (!tasksList.get(i).type.equals(taskType))
				tasksList.remove(tasksList.get(i));
			else
				i++;
		}

		return tasksList;
	}

	/**
	 * Get tasks or users or notes or UserStories associated with a certain
	 * event
	 * 
	 * @param eventId
	 *            The id of the event
	 * @param itemType
	 *            The type of the associated item
	 * @return
	 */
	public LinkedList getAssociatedwithEvent(String itemType) {
		LinkedList list = new LinkedList();
		switch (itemType.charAt(0)) {
		// tasks list
		case 'T':
			list.addAll(this.getEventTasks());
			return list;
			// Users list
		case 'U':
			list.addAll(this.getUsers());
			return list;
			// notes list
		case 'N':
			for (int i = 0; i < this.notes.size(); i++)
				list.add(this.notes.get(i));
			return list;
			// UserStories
		case 'S':
			list.addAll(Events.getEventUserStories(this));
			return list;
		}
		return list;
	}

	public List<User> getUsers() {

		List<User> list = new LinkedList<User>();
		List<EventUser> e = EventUser.find(
				"select e from EventUser e where event=? ", this).fetch();
		for (int i = 0; i < e.size(); i++) {
			list.add(e.get(i).user);
		}
		return list;
	}

	/**
	 * Returns all tasks associated with this event
	 * 
	 * @param event
	 *            the event
	 * @return all tasks associated with this event
	 * 
	 */
	public List<Task> getEventTasks() {
		List<Task> result = new LinkedList<Task>();
		if (!this.tasks.isEmpty())
			result.addAll(this.tasks);
		if (!this.userStories.isEmpty()) {
			for (int i = 0; i < this.userStories.size(); i++) {
				if (!this.userStories.get(i).tasks.isEmpty())
					result.addAll(this.userStories.get(i).tasks);
			}
		}
		Set<Task> set = new HashSet<Task>();
		set.addAll(result);
		List<Task> tasks = new LinkedList<Task>();
		tasks.addAll(set);
		return tasks;
	}

	/**
	 * This method returns a list of all Tasks in an event with a certain status
	 * 
	 * @param eventId
	 * @param taskStatus
	 * 
	 * @return List<Task>
	 */
	public List<Task> getEventTasksOfStatus(String taskStatus) {
		List<Task> tasksList = this.getAssociatedwithEvent("T");

		if (tasksList == null)
			return new LinkedList<Task>();

		int i = 0;
		while (i < tasksList.size()) {
			if (!tasksList.get(i).status.equals(taskStatus))
				tasksList.remove(tasksList.get(i));
			else
				i++;
		}

		return tasksList;
	}

	public static Event createEvent(String name, String description, long type,
			String location, GregorianCalendar startDate,
			GregorianCalendar endDate, long associatedID,
			boolean associateToProject, long userId) {
		
		Event e = new Event(name,description,location, startDate, endDate);		
		EventType eventType = EventType.findById(type);
		e.type = eventType;
		e.location = location;
		e.user = User.findById(userId);
		
		long projectId = 0;
		if(eventType.type.toLowerCase() == "sprint" || associateToProject){
			e.project = Project.findById(associatedID);
			System.out.println("AssociatedProjectID:" + associatedID);
			projectId = associatedID;
		}
		else{
			e.sprint = Event.findById(associatedID);
			System.out.println("AssociatedSprintID:" + associatedID);
			projectId = (Long) e.sprint.project.getEntityId();
		}
		System.out.println("UserID:" + userId);
		System.out.println("ProjectID:" + projectId);
		if (Users.getRoleInProject(userId, projectId).title.toLowerCase() == "scrum master")
			e.status = true;
		else {
			e.status = false;
			List<User> toNotify = UserRoleProject
					.find(
							"select urp.user from UserRoleProject urp where urp.role.title= ? and urp.project=?",
							"Scrum Master", Project.findById(projectId))
					.fetch();
			for (int i = 0; i < toNotify.size(); i++) {
				Notification notification = new Notification(
						(Long) toNotify.get(i).getEntityId(),
						"Event request",
						"You have a new event request")
						.save();
			}
		}			
		e.save();
		return e;
	}
	
}
